import os
import re
import sqlite3
from datetime import datetime
from typing import List, Dict, Optional, Set
from dataclasses import dataclass, field

from core.models import SoftwareInfo
from utils.pe_analyzer import pe_analyzer
from config.software_patterns import (
    SOFTWARE_PATTERNS, 
    is_excluded_directory, 
    get_category_from_name,
    match_software_signature,
    FILE_SIGNATURE_WEIGHTS
)
from utils.logger import setup_logger

logger = setup_logger()

class SoftwareIdentifier:
    """软件识别器核心类"""
    
    def __init__(self, db_path: str = "data/software_database.db"):
        """
        初始化软件识别器
        
        Args:
            db_path: 软件数据库路径
        """
        self.logger = setup_logger(__name__)
        self.db_path = db_path
        self._init_database()
        self._scanned_paths: Set[str] = set()  # 已扫描路径，避免重复扫描
    
    def _init_database(self):
        """初始化软件特征数据库"""
        try:
            # 确保数据库目录存在
            os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
            
            # 创建数据库连接
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            # 创建软件签名表
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS software_signatures (
                    id INTEGER PRIMARY KEY,
                    name TEXT NOT NULL,
                    version TEXT,
                    publisher TEXT,
                    category TEXT,
                    confidence_level REAL,
                    directory_pattern TEXT,
                    signature_files TEXT,
                    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            ''')
            
            # 创建索引以提高查询效率
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_name ON software_signatures(name)')
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_category ON software_signatures(category)')
            
            conn.commit()
            conn.close()
            self.logger.info(f"软件数据库初始化成功: {self.db_path}")
        except Exception as e:
            self.logger.error(f"软件数据库初始化失败: {str(e)}")
    
    def identify_software_in_directory(self, directory_path: str) -> List[SoftwareInfo]:
        """
        扫描目录识别软件
        
        Args:
            directory_path: 要扫描的目录路径
            
        Returns:
            List[SoftwareInfo]: 识别出的软件列表
        """
        self.logger.info(f"开始在目录中识别软件: {directory_path}")
        software_list = []
        potential_software_dirs = self._find_potential_software_directories(directory_path)
        
        for software_dir in potential_software_dirs:
            software_info = self._analyze_software_directory(software_dir)
            if software_info and software_info.confidence > 0.3:
                software_list.append(software_info)
                # 添加到数据库
                self.add_software_to_database(software_info)
        
        self.logger.info(f"软件识别完成，共识别到 {len(software_list)} 个软件")
        return software_list
    
    def _find_potential_software_directories(self, root_path: str) -> List[str]:
        """
        查找潜在的软件安装目录
        
        Args:
            root_path: 根目录路径
            
        Returns:
            List[str]: 潜在的软件目录列表
        """
        potential_dirs = []
        
        try:
            for dirpath, dirnames, filenames in os.walk(root_path):
                # 排除不需要扫描的目录
                if is_excluded_directory(dirpath):
                    dirnames[:] = []  # 跳过子目录
                    continue
                
                # 检查是否已扫描
                if dirpath in self._scanned_paths:
                    dirnames[:] = []
                    continue
                
                self._scanned_paths.add(dirpath)
                
                # 使用目录特征判断
                if self._is_software_directory(dirpath, filenames):
                    potential_dirs.append(dirpath)
                    self.logger.debug(f"找到潜在软件目录: {dirpath}")
                    # 避免重复扫描子目录
                    dirnames[:] = []
        except Exception as e:
            self.logger.error(f"查找软件目录失败: {str(e)}")
        
        return potential_dirs
    
    def _is_software_directory(self, dirpath: str, filenames: List[str]) -> bool:
        """
        判断目录是否为软件安装目录
        
        Args:
            dirpath: 目录路径
            filenames: 目录中的文件列表
            
        Returns:
            bool: 是否为软件目录
        """
        # 检查目录路径模式
        for pattern in SOFTWARE_PATTERNS['安装目录特征']:
            if re.search(pattern, dirpath, re.IGNORECASE):
                return True
        
        # 检查文件特征
        for filename in filenames:
            # 检查可执行文件特征
            for pattern in SOFTWARE_PATTERNS['可执行文件特征']:
                if re.search(pattern, filename, re.IGNORECASE):
                    return True
            # 检查是否有主程序和配置文件组合
            if filename.lower().endswith('.exe') and any(
                re.search(pattern, fname, re.IGNORECASE) 
                for pattern in SOFTWARE_PATTERNS['配置文件特征']
                for fname in filenames
            ):
                return True
        
        return False
    
    def _analyze_software_directory(self, directory_path: str) -> Optional[SoftwareInfo]:
        """
        分析软件目录，提取软件信息
        
        Args:
            directory_path: 软件目录路径
            
        Returns:
            Optional[SoftwareInfo]: 软件信息，如果识别失败返回None
        """
        try:
            # 查找主要可执行文件
            main_exe_path, signature_files = self._find_main_executable(directory_path)
            
            if not main_exe_path:
                return None
            
            # 提取软件信息
            software_info = self._extract_software_info(main_exe_path, directory_path)
            
            if not software_info:
                return None
            
            # 设置特征文件信息
            software_info.signature_files = signature_files
            software_info.directory_pattern = self._generate_directory_pattern(directory_path)
            
            # 计算统计信息
            self._calculate_directory_stats(software_info, directory_path)
            
            # 计算置信度
            software_info.confidence = self._calculate_confidence(software_info, signature_files)
            
            return software_info
            
        except Exception as e:
            self.logger.error(f"分析软件目录失败: {directory_path}, 错误: {str(e)}")
            return None
    
    def _find_main_executable(self, directory_path: str) -> tuple[Optional[str], List[str]]:
        """
        查找软件的主可执行文件
        
        Args:
            directory_path: 软件目录路径
            
        Returns:
            tuple: (主可执行文件路径, 特征文件列表)
        """
        main_exe = None
        signature_files = []
        exe_candidates = []
        
        for root, _, files in os.walk(directory_path):
            for file in files:
                file_path = os.path.join(root, file)
                
                # 检查是否为可执行文件
                if file.lower().endswith('.exe'):
                    # 排除卸载程序
                    if re.search(r'unins.*\.exe', file, re.IGNORECASE):
                        signature_files.append(file_path)
                        continue
                    
                    # 排除安装程序
                    if re.search(r'(setup|install)\.exe', file, re.IGNORECASE):
                        signature_files.append(file_path)
                        continue
                    
                    exe_candidates.append((file_path, file))
                
                # 检查配置文件
                for pattern in SOFTWARE_PATTERNS['配置文件特征']:
                    if re.search(pattern, file, re.IGNORECASE):
                        signature_files.append(file_path)
                        break
        
        # 选择最可能的主程序
        if exe_candidates:
            # 优先选择与目录名相似的可执行文件
            dir_name = os.path.basename(directory_path).lower()
            
            for exe_path, exe_name in exe_candidates:
                exe_base = os.path.splitext(exe_name)[0].lower()
                if exe_base in dir_name or dir_name in exe_base:
                    main_exe = exe_path
                    break
            
            # 如果没有匹配，选择第一个可执行文件
            if not main_exe:
                main_exe = exe_candidates[0][0]
        
        # 添加主程序到特征文件
        if main_exe and main_exe not in signature_files:
            signature_files.append(main_exe)
        
        return main_exe, signature_files
    
    def _extract_software_info(self, exe_path: str, install_path: str) -> Optional[SoftwareInfo]:
        """
        从可执行文件中提取软件信息
        
        Args:
            exe_path: 可执行文件路径
            install_path: 安装路径
            
        Returns:
            Optional[SoftwareInfo]: 软件信息
        """
        # 使用PE分析器提取信息
        exe_info = pe_analyzer.get_software_info_from_exe(exe_path)
        
        if not exe_info['name']:
            # 如果无法提取信息，使用文件名作为软件名
            exe_info['name'] = os.path.splitext(os.path.basename(exe_path))[0]
        
        # 匹配常见软件签名
        common_match = match_software_signature(os.path.basename(exe_path), pe_analyzer.extract_exe_info(exe_path))
        
        if common_match:
            # 使用匹配结果
            name = common_match['name']
            publisher = common_match['publisher']
            category = common_match['category']
        else:
            # 否则自己分类
            name = exe_info['name']
            publisher = exe_info['publisher']
            category = get_category_from_name(name, publisher, exe_info['description'])
        
        # 创建SoftwareInfo对象
        software_info = SoftwareInfo(
            name=name,
            version=exe_info['version'],
            publisher=publisher,
            description=exe_info['description'],
            install_path=install_path,
            executable_path=exe_path,
            category=category
        )
        
        # 设置安装日期（使用目录的创建时间）
        try:
            if os.path.exists(install_path):
                install_time = os.path.getctime(install_path)
                software_info.install_date = datetime.fromtimestamp(install_time)
        except:
            pass
        
        return software_info
    
    def _calculate_directory_stats(self, software_info: SoftwareInfo, directory_path: str):
        """
        计算目录统计信息
        
        Args:
            software_info: 软件信息对象
            directory_path: 目录路径
        """
        try:
            file_count = 0
            total_size = 0
            
            for root, _, files in os.walk(directory_path):
                for file in files:
                    try:
                        file_path = os.path.join(root, file)
                        file_size = os.path.getsize(file_path)
                        total_size += file_size
                        file_count += 1
                    except:
                        continue
            
            software_info.file_count = file_count
            software_info.total_size = total_size
        except Exception as e:
            self.logger.error(f"计算目录统计失败: {str(e)}")
    
    def _calculate_confidence(self, software_info: SoftwareInfo, signature_files: List[str]) -> float:
        """
        计算软件识别的置信度
        
        Args:
            software_info: 软件信息
            signature_files: 特征文件列表
            
        Returns:
            float: 置信度（0.0-1.0）
        """
        confidence = 0.0
        
        # 基于文件特征计算置信度
        for file_path in signature_files:
            file_name = os.path.basename(file_path).lower()
            
            if re.search(r'unins.*\.exe', file_name):
                confidence += FILE_SIGNATURE_WEIGHTS['uninstaller'] * 0.1
            elif re.search(r'(setup|install)\.exe', file_name):
                confidence += FILE_SIGNATURE_WEIGHTS['setup'] * 0.1
            elif file_name.endswith('.exe'):
                confidence += FILE_SIGNATURE_WEIGHTS['main_exe'] * 0.3
            elif any(ext in file_name for ext in ['.ini', '.cfg', '.config']):
                confidence += FILE_SIGNATURE_WEIGHTS['config'] * 0.05
            elif 'license' in file_name or 'readme' in file_name:
                confidence += FILE_SIGNATURE_WEIGHTS['license'] * 0.05
        
        # 基于信息完整性增加置信度
        if software_info.name:
            confidence += 0.1
        if software_info.version != 'Unknown':
            confidence += 0.1
        if software_info.publisher != 'Unknown':
            confidence += 0.1
        if software_info.description:
            confidence += 0.1
        
        # 标准化到0-1范围
        return min(confidence, 1.0)
    
    def _generate_directory_pattern(self, directory_path: str) -> str:
        """
        生成目录模式字符串
        
        Args:
            directory_path: 目录路径
            
        Returns:
            str: 目录模式
        """
        # 提取目录的最后两级作为模式
        parts = directory_path.split(os.sep)
        if len(parts) >= 2:
            return os.path.join(parts[-2], parts[-1])
        elif len(parts) == 1:
            return parts[0]
        return directory_path
    
    def add_software_to_database(self, software_info: SoftwareInfo) -> bool:
        """
        添加软件到特征数据库
        
        Args:
            software_info: 软件信息
            
        Returns:
            bool: 是否成功添加
        """
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            # 检查是否已存在
            cursor.execute(
                'SELECT id FROM software_signatures WHERE name = ? AND version = ?',
                (software_info.name, software_info.version)
            )
            existing = cursor.fetchone()
            
            signature_files_str = ';'.join(software_info.signature_files)
            
            if existing:
                # 更新现有记录
                cursor.execute('''
                    UPDATE software_signatures 
                    SET publisher = ?, category = ?, confidence_level = ?, 
                        directory_pattern = ?, signature_files = ?, updated_time = CURRENT_TIMESTAMP
                    WHERE id = ?
                ''', (
                    software_info.publisher, 
                    software_info.category, 
                    software_info.confidence,
                    software_info.directory_pattern,
                    signature_files_str,
                    existing[0]
                ))
            else:
                # 插入新记录
                cursor.execute('''
                    INSERT INTO software_signatures 
                    (name, version, publisher, category, confidence_level, 
                     directory_pattern, signature_files, created_time, updated_time)
                    VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
                ''', (
                    software_info.name,
                    software_info.version,
                    software_info.publisher,
                    software_info.category,
                    software_info.confidence,
                    software_info.directory_pattern,
                    signature_files_str
                ))
            
            conn.commit()
            conn.close()
            return True
            
        except Exception as e:
            self.logger.error(f"添加软件到数据库失败: {str(e)}")
            return False
    
    def get_software_from_database(self, category: Optional[str] = None) -> List[SoftwareInfo]:
        """
        从数据库获取软件信息
        
        Args:
            category: 可选的分类过滤
            
        Returns:
            List[SoftwareInfo]: 软件列表
        """
        software_list = []
        
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            if category:
                cursor.execute(
                    'SELECT name, version, publisher, category, directory_pattern FROM software_signatures WHERE category = ?',
                    (category,)
                )
            else:
                cursor.execute(
                    'SELECT name, version, publisher, category, directory_pattern FROM software_signatures'
                )
            
            rows = cursor.fetchall()
            for row in rows:
                # 创建基本的SoftwareInfo对象
                software_info = SoftwareInfo(
                    name=row[0],
                    version=row[1],
                    publisher=row[2],
                    description="",
                    install_path="",  # 数据库中没有保存完整路径
                    executable_path="",
                    category=row[3]
                )
                software_list.append(software_info)
            
            conn.close()
        except Exception as e:
            self.logger.error(f"从数据库获取软件失败: {str(e)}")
        
        return software_list
    
    def clear_scanned_paths(self):
        """清除已扫描路径缓存"""
        self._scanned_paths.clear()